/* 
 *	Copyright (C) 2007 cooleyes
 *	eyes.cooleyes@gmail.com 
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */
 
#include "tta_parser.h"
#include "common/mem64.h"
#include <ttacodec.h>
#include <string.h>

#ifdef _BIG_ENDIAN
#define	ENDSWAP_INT16(x)	(((((x)>>8)&0xFF)|(((x)&0xFF)<<8)))
#define	ENDSWAP_INT32(x)	(((((x)>>24)&0xFF)|(((x)>>8)&0xFF00)|(((x)&0xFF00)<<8)|(((x)&0xFF)<<24)))
#else
#define	ENDSWAP_INT16(x)	(x)
#define	ENDSWAP_INT32(x)	(x)
#endif


TtaParser::TtaParser() {
	initialize();
};

TtaParser::~TtaParser() {
	close();
};

void TtaParser::initialize() {
	is_open = false;
	
	total_frames = 0;
	total_samples = 0;
	current_frame = 0;
	
	frame_index = 0;	
	reader = 0;
	output_buffer = 0;
	read_buffer = 0;
	
	codec = 0;
	codec_initialize = false;
	
	memset(&init_data, 0, sizeof(AudioCodecInitData));
};

void TtaParser::finalize() {
	if ( reader ) {
		buffered_reader_close(reader);
	}
	if ( frame_index ) {
		free(frame_index);
	}
	if ( output_buffer ) {
		free(output_buffer);
	}
	if ( read_buffer ) {
		free(read_buffer);
	}
	if ( codec ) {
		if ( codec_initialize )
			codec->finalize();
		delete codec;
	}
	initialize();
};

char* TtaParser::skip_id3v2_and_get_header_info() {
	uint8_t id3v2_header_buffer[4];
	if ( buffered_reader_read(reader, id3v2_header_buffer, 4) != 4 ) {
		finalize();
		return "TtaParser : read ID3v2 header fail";
	}
	if ( id3v2_header_buffer[0] == 'I' && id3v2_header_buffer[1] == 'D' && id3v2_header_buffer[2] == '3' ) {
		uint8_t id3v2_buffer[6];
		if ( buffered_reader_read(reader, id3v2_buffer, 6) != 6 ) {
			finalize();
			return "TtaParser : read ID3v2 fail";
		}
		uint32_t id3v2_size, temp_value;
		temp_value = id3v2_buffer[2];
		temp_value = ( temp_value & 0x7F ) << 21;
		id3v2_size = temp_value;
		temp_value = id3v2_buffer[3];
		temp_value = ( temp_value & 0x7F ) << 14;
		id3v2_size = id3v2_size | temp_value;
		temp_value = id3v2_buffer[4];
		temp_value = ( temp_value & 0x7F ) << 7;
		id3v2_size = id3v2_size | temp_value;
		temp_value = id3v2_buffer[5];
		temp_value = ( temp_value & 0x7F ) ;
		id3v2_size = id3v2_size | temp_value;
		buffered_reader_seek(reader, buffered_reader_position(reader) + id3v2_size);
	}
	else
		buffered_reader_seek(reader, 0);
	
	TTA_header tta_header;
	if ( buffered_reader_read( reader, &tta_header, sizeof(TTA_header) ) !=  sizeof(TTA_header)) {
		finalize();
		return "TtaParser : read TTA header fail";
	}
	
	if (ENDSWAP_INT32(tta_header.TTAid) != TTA1_SIGN) {
		finalize();
		return "TtaParser : TTA1_SIGN error";
	}
	
	tta_header.CRC32 = ENDSWAP_INT32(tta_header.CRC32);
	// Check header crc32 
	unsigned long checksum = tta_crc32((unsigned char *) &tta_header,
		sizeof(TTA_header) - sizeof(long));
	if (checksum != tta_header.CRC32) {
		finalize();
		return "TtaParser : TTA Header CRC error";
	}
				
	init_data.samplerate = ENDSWAP_INT32(tta_header.SampleRate);
	
	init_data.channels = ENDSWAP_INT16(tta_header.NumChannels);
	
	if ( init_data.channels < 1 || init_data.channels > 2) {
		finalize();
		return "TtaParser : channels != 1 && channels != 2";
	}
		
	init_data.sample_bits = ENDSWAP_INT16(tta_header.BitsPerSample); 
	if ( init_data.sample_bits != 0x10) {
		finalize();
		return "TtaParser : sample_bits != 16";
	} 
	
	init_data.samples_per_frame = tta_codec_get_frame_len(init_data.samplerate);
	init_data.samples_per_decoded = 2304;
	
	total_samples = ENDSWAP_INT32(tta_header.DataLength);
	uint32_t last_frame_samples = total_samples % init_data.samples_per_frame;
	total_frames = total_samples / init_data.samples_per_frame + (last_frame_samples ? 1 : 0);
	
	if ( total_frames == 0 ) {
		finalize();
		return "TtaParser : total_frames = 0";
	}
	
	return 0;	
};

char* TtaParser::open(const char* filename) {
	
	reader = buffered_reader_open(filename, 32768, 0);
	if ( !reader ){
		finalize();
		return "TtaParser : open buffered_reader fail";
	}
	
	char* result;
	buffered_reader_seek(reader, 0);
	result = skip_id3v2_and_get_header_info();
	if ( result != 0 )
		return result;
	
	unsigned long seek_table[total_frames+1];
	if ( buffered_reader_read( reader, seek_table, (total_frames+1) * sizeof(unsigned long) ) !=  (total_frames+1) * sizeof(unsigned long) ) {
		finalize();
		return "TtaParser : read TTA seek_table fail";
	}		
	
	if ( tta_crc32((unsigned char *) seek_table, (total_frames * sizeof(unsigned long))) != seek_table[total_frames] ) {
		finalize();
		return "TtaParser : TTA seek_table CRC error";
	}
	
	tta_data_start = buffered_reader_position(reader);
	
	frame_index = (AudioFrameIndex*)malloc_64(total_frames * sizeof(AudioFrameIndex));
	if ( frame_index == 0 ) {
		finalize();
		return "TtaParser : malloc seek_table fail";
	}
	uint32_t i;
	frame_index[0].frame_position = tta_data_start;
	frame_index[0].frame_size = seek_table[0];
	init_data.frame_align = frame_index[0].frame_size;
	for( i = 1; i < total_frames; i++ ) {
		frame_index[i].frame_position = frame_index[i-1].frame_position + frame_index[i-1].frame_size;
		frame_index[i].frame_size = seek_table[i]; 
		if ( init_data.frame_align < frame_index[i].frame_size )
			init_data.frame_align = frame_index[i].frame_size;
	}
	
	buffered_reader_seek(reader, tta_data_start);
	
	read_buffer = malloc_64(init_data.frame_align);
	if ( read_buffer == 0 ) {
		finalize();
		return "TtaParser : malloc read_buffer fail";
	}
	
	output_buffer = malloc_64(init_data.samples_per_decoded<<2);
	if ( output_buffer == 0 ) {
		finalize();
		return "TtaParser : malloc output_buffer fail";
	}
	
	codec = AudioCodec::create_audio_codec(AudioCodec::TTA);
	if ( !codec ) {
		finalize();
		return "TtaParser : create audio codec fail";
	}
	result = codec->initialize(&init_data);
	if ( result ) {
		finalize();
		return result;
	}
	codec_initialize = true;
	
	is_open = true;
	return 0;
};

void TtaParser::close() {
	if ( is_open ) {
		finalize();
	}
};
	
uint32_t TtaParser::get_samplerate() {
	return init_data.samplerate;
};

uint32_t TtaParser::get_channels() {
	return init_data.channels;
};

uint64_t TtaParser::get_duration() {
	uint64_t duration = 1000000LL*total_samples/init_data.samplerate;
	return duration;
};
	
uint32_t TtaParser::get_samples_per_frame() {
	return init_data.samples_per_decoded;
};

uint32_t TtaParser::get_max_samples_per_frame() {
	return init_data.samples_per_decoded;
};

uint64_t TtaParser::seek_time(uint64_t time) {
	uint32_t samples = (uint32_t)(time*init_data.samplerate/1000000LL);
	if ( samples > total_samples )
		samples = total_samples;
	
	current_frame = samples/init_data.samples_per_frame;
	
	if (current_frame >= total_frames)
		current_frame = total_frames - 1;
	
	buffered_reader_seek(reader, frame_index[current_frame].frame_position);
	codec->reset();
	
	uint64_t current = 1000000LL*current_frame*init_data.samples_per_frame/init_data.samplerate;
	return current;
};

char* TtaParser::get_frame(void** pcm, uint32_t* samples) {
	
	if ( !is_open )
		return "TtaParser : parser is not open";
	
	memset(output_buffer, 0, init_data.samples_per_decoded<<2);
	uint32_t read_size = frame_index[current_frame].frame_size;
	if ( codec->frame_decode_finished() ) {
		memset(read_buffer, 0, init_data.frame_align);
		if ( (read_size = buffered_reader_read(reader, read_buffer, read_size)) != read_size ) {
			*pcm = output_buffer;
			*samples = 0;
			return 0;
		}
		current_frame++;
	}
	int32_t get_samples = codec->decode(read_buffer, read_size, output_buffer);
	*samples = (get_samples<0) ? 0 : get_samples;
	if ( *samples > 0 && init_data.channels == 1 ) {
		int32_t i;
		uint16_t* p = (uint16_t*)output_buffer;
		for(i=*samples-1; i>=0; i--) {
			p[2*i+1] = p[i];
			p[2*i] = p[i];
		} 
	}
	*pcm = output_buffer;
		
	return 0;
};



